/*--------------------------------------------------------------------------------------+
|
|     $Source: samples/SampleDgnDbEditor/SampleDgnDbEditor.cpp $
|
|  $Copyright: (c) 2014 Bentley Systems, Incorporated. All rights reserved. $
|
+--------------------------------------------------------------------------------------*/
#include <MobileDgn/MobileDgnApplication.h>
#include "SampleDgnDbEditor.h"
#include "DeleteShippingItemTool.h"
#include "MoveShipping%Class%Tool.h"
#include "PlaceShippingCrateTool.h"
#include "PlaceShipping%Class%Tool.h"

#if defined (_WIN32)
#include <windows.h>
//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
BOOL APIENTRY DllMain (HMODULE hModule, DWORD reasonForCall, LPVOID lpReserved)
    {
    if (DLL_PROCESS_ATTACH == reasonForCall)
        MobileDgnApplication::RegisterApplication (*new SampleDgnDbEditorApp());

  #if !defined (NDEBUG)
    AllocConsole();
    freopen("CONOUT$", "wb", stdout);
  #endif

    return TRUE;
    }
#endif

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
void SampleDgnDbEditorApp::_OnMobileDgnInitialized()
    {
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
void SampleDgnDbEditorApp::_OnProjectOpen()
    {
    SendMessageToUiThread (MESSAGE_ProjectSelected(), Project()->GetDbFileName());

    if (!Project()->GetDgnFile().IsReadOnly())
        {
        ImportECSchema (L"SampleDgnDbEditor.01.00.ecschema.xml");
        Project()->SaveSettings();
        Project()->SaveChanges();
        Project()->GetTxnManager().Activate();
        }
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
void SampleDgnDbEditorApp::_OnProjectClose ()
    {
    T_Super::_OnProjectClose();
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
bool SampleDgnDbEditorApp::_OnAbstractUiStateChanged (Utf8CP key)
    {
    return false;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
DgnTool* SampleDgnDbEditorApp::_OnStartTool (MobileDgnViewport& viewport, Utf8CP toolId)
    {
    DEBUG_PRINT ("SampleDgnDbEditorApp::_OnStartTool - toolId=%s", toolId);

    if (0 == strcmp (TOOLID_DeleteShippingItem(), toolId))
        return new DeleteShippingItemTool (*viewport.GetWriteModel());

    if (0 == strcmp (TOOLID_MoveShipping%Class%(), toolId))
        return new MoveShipping%Class%Tool (*viewport.GetWriteModel());

    if (0 == strcmp (TOOLID_PlaceShippingCrate(), toolId))
        return new PlaceShippingCrateTool (*viewport.GetWriteModel());

    if (0 == strcmp (TOOLID_PlaceShipping%Class%(), toolId))
        return new PlaceShipping%Class%Tool (*viewport.GetWriteModel());

    // not recognized, let it pass through...
    return NULL;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
StatusInt SampleDgnDbEditorApp::DoSaveView()
    {
    if (!Project())
        return BSIERROR;

    MobileDgnViewport* viewportP = MobileDgnApplication::GetViewportP (0);
    if (NULL == viewportP)
        return BSIERROR;

    ViewControllerP viewControllerP = viewportP->GetViewControllerP();
    if (NULL == viewControllerP)
        return BSIERROR;

    // WIP: ensure a unique name, would be better to prompt through UI
    Utf8PrintfString viewName ("View-%lu", BeTimeUtilities::QueryMillisecondsCounter());

    DgnViewId viewId;
    return viewControllerP->SaveTo (viewName, viewId);
    }

//---------------------------------------------------------------------------------------
// @bsimethod                                                   BentleySystems
//---------------------------------------------------------------------------------------
StatusInt SampleDgnDbEditorApp::DoGenerateDrawing()
    {
    if (!Project())
        return BSIERROR;

    DgnModels::Row row;
    row.SetName ("drawing1");
    row.SetModelType (DgnModelType::Drawing);

    if (DGNMODEL_STATUS_Success != Project()->GetDgnFile().CreateNewModel (row))
        return BSIERROR;

    DgnModelP drawingModelP = Project()->Models().GetModelById (row.GetId());
    if (NULL == drawingModelP)
        return BSIERROR;

    ECClassP classP = NULL;
    if (BSISUCCESS != Project()->GetEC().GetSchemaManager().GetECClass (classP, "SampleDgnDbEditor", "ShippingCrate"))
        return BSIERROR;

    ECSqlStatement statement;
    ECSqlStatus prepareStatus = statement.Prepare (*Project(), 
        "SELECT ECInstanceId,Placement.Origin,Placement.Yaw,Placement.Pitch,Placement.Roll,ExteriorLength,ExteriorWidth "
        "FROM SampleDgnDbEditor.ShippingCrate");

    if (ECSqlStatus::Success != prepareStatus)
        return BSIERROR;

    while (ECSqlStepStatus::HasRow == statement.Step())
        {
        DrawingGraphicsPtr graphicsPtr = drawingModelP->CreateDrawingGraphics();
        if (!graphicsPtr.IsValid())
            return BSIERROR;

        ECInstanceId instanceId = statement.GetValueId<ECInstanceId> (0);

        DPoint3d placementOrigin = statement.GetValuePoint3D (1);
        YawPitchRollAngles placementAngles = YawPitchRollAngles::FromDegrees (statement.GetValueDouble (2), statement.GetValueDouble (3), statement.GetValueDouble (4));

        double exteriorLength = statement.GetValueDouble (5);
        double exteriorWidth = statement.GetValueDouble (6);

        CurveVectorPtr rectanglePtr = CurveVector::CreateRectangle (0.0, 0.0, exteriorLength, exteriorWidth, 0.0);
        if (!rectanglePtr.IsValid())
            return BSIERROR;

        Transform localToWorld = placementAngles.ToTransform (placementOrigin);
        rectanglePtr->TransformInPlace (localToWorld);

        if (BSISUCCESS != graphicsPtr->AddCurveVector (*rectanglePtr))
            return BSIERROR;

        // relate the graphical representation with the business data
        if (BSISUCCESS != DgnECPersistence::SetPrimaryInstanceOnElement (graphicsPtr->GetElementHandleR(), ECInstanceKey (classP->GetId(), instanceId), *Project()))
            return BSIERROR;

        if (!graphicsPtr->Save().IsValid())
            return BSIERROR;
        }

    Project()->GetTxnManager().CloseCurrentTxn();    
    return BSISUCCESS;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
void SampleDgnDbEditorApp::DoProjectUndo()
    {
    if (Project())
        Project()->GetTxnManager().ReverseSingleTxn();
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
void SampleDgnDbEditorApp::DoProjectRedo()
    {
    if (Project() && Project()->GetTxnManager().RedoIsPossible())
        Project()->GetTxnManager().ReinstateTxn();
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
bool SampleDgnDbEditorApp::_OnMessageReceived (Utf8CP messageType, JsonValueCR messageObj)
    {
    if (0 == strcmp (MESSAGE_SaveView(), messageType))
        {
        DoSaveView();
        return true;
        }

    if (0 == strcmp (MESSAGE_GenerateDrawing(), messageType))
        {
        DoGenerateDrawing();
        return true;
        }

    if (0 == strcmp (MESSAGE_ProjectUndo(), messageType))
        {
        DoProjectUndo();
        return true;
        }

    if (0 == strcmp (MESSAGE_ProjectRedo(), messageType))
        {
        DoProjectRedo();
        return true;
        }

    DEBUG_PRINT ("NOT HANDLED: SampleDgnDbEditorApp::_OnMessageReceived - type=%s", messageType);
    return false;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
bool SampleDgnDbEditorApp::_GetUiConfigurationFilePath (BeFileNameR path) const
    {
    path = GetAssetsRootDirectory();
    path.AppendToPath (L"UIConfig");
    path.AppendToPath (L"ui.json");
    return true;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
StatusInt SampleDgnDbEditorApp::ImportECSchema (WCharCP schemaName)
    {
    if (NULL == Project())
        return BSIERROR;

    BeFileName ecSchemaFile (GetAssetsRootDirectory());
    ecSchemaFile.AppendToPath (schemaName);

    if (!ecSchemaFile.DoesPathExist())
        return BSIERROR;

    ECSchemaReadContextPtr contextPtr = ECSchemaReadContext::CreateContext();
    contextPtr->AddSchemaLocater (Project()->GetEC().GetSchemaLocater());
    contextPtr->AddSchemaPath (ecSchemaFile.GetDirectoryName().GetName());

    ECSchemaPtr schema;
    SchemaReadStatus readSchemaStatus = ECSchema::ReadFromXmlFile (schema, ecSchemaFile.GetName(), *contextPtr);
    if (SCHEMA_READ_STATUS_Success != readSchemaStatus)
        return readSchemaStatus;

    BentleyStatus importSchemaStatus = Project()->GetEC().GetSchemaManager().ImportECSchemas (contextPtr->GetCache());
    if (SUCCESS != importSchemaStatus)
        return importSchemaStatus;

    return BSISUCCESS;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
PhysicalGraphicsPtr DgnGraphicsUtils::CreatePhysicalGraphicsFromPhysicalGeometry (DgnModelR writeModel, PhysicalGeometryCR physicalGeometry, TransformCR localToWorld)
    {
    // create the graphics container
    PhysicalGraphicsPtr graphicsPtr = writeModel.CreatePhysicalGraphics();
    if (!graphicsPtr.IsValid())
        return NULL;

    for (IGeometryPtr geom : physicalGeometry)
        {
        switch (geom.GetType())
            {
            case IGeometryType_CurvePrimitive:
                {
                ICurvePrimitivePtr curvePtr = geom.GetAsICurvePrimitive();
                curvePtr->TransformInPlace (localToWorld);
                graphicsPtr->AddCurvePrimitive (*curvePtr);
                }
                break;

            case IGeometryType_CurveVector:
                {
                CurveVectorPtr curveVectorPtr = geom.GetAsCurveVector();
                curveVectorPtr->TransformInPlace (localToWorld);
                graphicsPtr->AddCurveVector (*curveVectorPtr);
                }
                break;

            case IGeometryType_SolidPrimitive:
                {
                ISolidPrimitivePtr solidPtr = geom.GetAsISolidPrimitive();
                solidPtr->TransformInPlace (localToWorld);
                graphicsPtr->AddSolidPrimitive (*solidPtr);
                }
                break;

            case IGeometryType_Polyface:
            case IGeometryType_MSBsplineCurve:
            case IGeometryType_MSBsplineSurface:
            default:
                break;
            }
        }

    // tell container that adds are complete so the element can be generated and range calculated
    if (BSISUCCESS != graphicsPtr->Finish())
        return NULL;

    return graphicsPtr;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
PhysicalGraphicsPtr DgnGraphicsUtils::GetItemPhysicalGraphics (PhysicalItemHandlerR itemHandler, DgnModelR writeModel, IECInstanceCR item)
    {
    PhysicalGeometry geometry;
    if (BSISUCCESS != itemHandler.GetPhysicalGeometry (geometry, item))
        return NULL;

    Transform localToWorld;
    if (BSISUCCESS != itemHandler.GetPlacement (localToWorld, item))
        return NULL;

    PhysicalGraphicsPtr graphicsPtr = CreatePhysicalGraphicsFromPhysicalGeometry (writeModel, geometry, localToWorld);
    if (!graphicsPtr.IsValid())
        return NULL;

    return graphicsPtr;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
StatusInt DgnGraphicsUtils::InsertItemPhysicalGraphics (ElementId* elementIdP, PhysicalItemHandlerR itemHandler, DgnModelR writeModel, ECInstanceKeyCR itemKey)
    {
    PhysicalGeometry geometry;
    Transform localToWorld;

    // get the physical geometry for the item instance
    if (BSISUCCESS != itemHandler.GetPhysicalGeometry (geometry, localToWorld, writeModel.GetDgnProject(), itemKey))
        return BSIERROR;

    PhysicalGraphicsPtr graphicsPtr = CreatePhysicalGraphicsFromPhysicalGeometry (writeModel, geometry, localToWorld);
    if (!graphicsPtr.IsValid())
        return BSIERROR;

    // relate the graphical representation with the business data
    if (BSISUCCESS != DgnECPersistence::SetPrimaryInstanceOnElement (graphicsPtr->GetElementHandleR(), itemKey, writeModel.GetDgnProject()))
        return BSIERROR;

    // persist the graphical element
    ElementId elementId = graphicsPtr->Save();
    if (!elementId.IsValid())
        return BSIERROR;

    if (elementIdP)
        *elementIdP = elementId;

    return BSISUCCESS;
    }

//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
StatusInt DgnGraphicsUtils::UpdateItemPhysicalGraphics (PhysicalItemHandlerR itemHandler, DgnModelR writeModel, ECInstanceKeyCR itemKey, ElementId elementId)
    {
    PhysicalGeometry geometry;
    Transform localToWorld;

    // get the physical geometry for the item instance
    if (BSISUCCESS != itemHandler.GetPhysicalGeometry (geometry, localToWorld, writeModel.GetDgnProject(), itemKey))
        return BSIERROR;

    PhysicalGraphicsPtr graphicsPtr = CreatePhysicalGraphicsFromPhysicalGeometry (writeModel, geometry, localToWorld);
    if (!graphicsPtr.IsValid())
        return BSIERROR;

    // replace the original element
    EditElementHandle originalElement (elementId, writeModel);
    graphicsPtr->GetElementHandleR().ReplaceInModel (originalElement.GetElementRef());
    return BSISUCCESS;
    }
